/**
 * 
 */
package com.heo.util.login;

import java.text.DateFormat;
import java.util.Date;
import java.util.Hashtable;

import org.apache.log4j.Logger;

/**
 * @author ChiFei
 *         登录安全检查工具类
 *         如果需要对IP进行过滤，可以把ip信息写入到登录名中。这样当某个用户在某个ip被禁用后，不影响该用户从其他IP地址登录。
 *         例：1000,admin,219.133.55.103
 *         用法：
 *         当出现登录失败时，调用下面的方法记录登录用户的信息
 *         LoginSecurity.getInstance().addErrLogin(orgId + "," + username + "," + ipAddress);// 记录登录错误
 *         当登录成功时，校验一下内存里的信息，是否触发暴力破解的临界点，如果是，就返回登录错误
 *         if (!LoginSecurity.getInstance().checkLogin(orgId + "," + username + "," + ipAddress))
 *         {
 *         // 如果内存中发现有暴力破解的情况，则即使密码是对的，也要返回错误
 *         return MessageCode.USERNAME_OR_PASSWORD_IS_ERROR;
 *         }
 */
public class LoginSecurity
{
  private static Logger log = Logger.getLogger(LoginSecurity.class);
  private static final int LOGIN_ERROR_NUMBER = 10;// 单位时间内的登录错误次数
  private static final int LOGIN_PER_TIME = 60;// 单位时间，单位：秒
  private static final int BLOCK_ACCESS_TIME = 3600;// 禁止访问的时长，单位：秒

  private static LoginSecurity loginSecurity = new LoginSecurity();

  private static Hashtable<String, Long> loginErrCount = new Hashtable<String, Long>();// 记录用户登录错误的次数，key：用户名，value：错误次数
  private static Hashtable<String, Long> loginErrTime = new Hashtable<String, Long>();// 记录用户登录错误的起始时间。
  private static Hashtable<String, Long> loginLockTime = new Hashtable<String, Long>();// 记录用户登录锁定的结束时间。这个时间之后才可以重新校验

  private LoginSecurity()
  {
  }

  public static LoginSecurity getInstance()
  {
    return loginSecurity;
  }

  /**
   * 登录成功后，调用此方法检查。当该方法返回ture时，才正式进入登录成功流程
   * 如果超过规定的次数，即使登录成功，也会返回错误。
   * 
   * @param loginName
   * @return
   */
  public boolean checkLogin(String loginName)
  {
    // 判断该用户是否已经超限被锁住
    if (loginErrCount.get(loginName) == null || loginLockTime.get(loginName) == null || new Date().getTime() > loginLockTime.get(loginName))
    {
      log.debug("[登录安全模块]>>>>>>用户未被限制或登录限制已被解除，登录信息重新初始化，登录成功。用户名：" + loginName);
      loginErrTime.remove(loginName);
      loginErrCount.remove(loginName);
      loginLockTime.remove(loginName);
      return true;
    }
    else
    {
      DateFormat df = DateFormat.getDateTimeInstance();
      log.debug("[登录安全模块]>>>>>>用户密码正确，但因超限导致登录失败。用户名：" + loginName + " 解锁时间：" + df.format(new Date(loginLockTime.get(loginName))));
      return false;
    }
  }

  /**
   * 登录失败后，调用此方法
   * 
   * @param loginName
   */
  public void addErrLogin(String loginName)
  {
    if (loginErrCount.get(loginName) == null)
    {
      // 第一次登录失败
      loginErrCount.put(loginName, new Long(1));
      loginErrTime.put(loginName, new Date().getTime());
      log.debug("[登录安全模块]>>>>>>第1次登录失败，用户名：" + loginName);
    }
    else
    {
      loginErrCount.put(loginName, loginErrCount.get(loginName) + 1);
      String deBlockTime = "无";
      if (loginLockTime.get(loginName) != null)
      {
        DateFormat df = DateFormat.getDateTimeInstance();
        deBlockTime = df.format(new Date(loginLockTime.get(loginName)));
      }
      log.debug("[登录安全模块]>>>>>>第" + loginErrCount.get(loginName) + "次登录失败，用户名：" + loginName + " 解锁时间：" + deBlockTime);
      countTime(loginName);
    }
  }

  /**
   * 对用户的错误登录次数做统计
   * 
   * @param loginName
   */
  private void countTime(String loginName)
  {
    // 计算有没有在规定的时间内超过登录次数
    long errTime = loginErrTime.get(loginName);// 第一次登录错误的时间
    long nowTime = new Date().getTime();
    long errCount = loginErrCount.get(loginName);// 登录错误的次数
    if ((nowTime - errTime) / 1000 < LOGIN_PER_TIME)
    {
      // 如果在间隔时间内，超过规定的次数
      if (errCount > LOGIN_ERROR_NUMBER)
      {
        if (loginLockTime.get(loginName) == null)
        {
          log.debug("[登录安全模块]>>>>>>错误登录次数超过规则限制，用户名：" + loginName);
          loginLockTime.put(loginName, nowTime + BLOCK_ACCESS_TIME * 1000);
        }
        else
        {
          DateFormat df = DateFormat.getDateTimeInstance();
          log.debug("[登录安全模块]>>>>>>用户已被限制登录，用户名：" + loginName + " 解锁时间：" + df.format(new Date(loginLockTime.get(loginName))));
        }
      }
      else
      {

      }
    }
    else
    {
      if (loginLockTime.get(loginName) == null || nowTime > loginLockTime.get(loginName))
      {
        log.debug("[登录安全模块]>>>>>>重新初始化登录限制，用户名：" + loginName);
        // 重新初始化第一次错误登录的时间
        loginErrTime.remove(loginName);
        // 重新初始化第一次登录错误计数
        loginErrCount.remove(loginName);
        loginLockTime.remove(loginName);
      }
    }
  }

}
